/*
    Aufgabe 1) Zweidimensionale Arrays und Rekursion - TicTacToe
*/

import java.awt.*;
import java.util.Arrays;

public class Aufgabe1 {
    public static void main (String[] args) throws InterruptedException {

        int size = 600;
        StdDraw.setCanvasSize(size, size);
        StdDraw.setXscale(0,size);
        StdDraw.setYscale(0,size);
        StdDraw.setPenRadius(0.002);
        
        char[][] gameBoard = new char[][] {
            {' ', ' ', ' '},
            {' ', ' ', ' '},
            {' ', ' ', ' '}
        };

        // true  -> human vs. human
        // false -> human vs. computer
        boolean twoPlayer = false;
        // true  -> human
        // false -> computer
        boolean player = true;
        int maxDepth = 3;
        char token;
        int[] analysis;

        // GAME LOOP
        do {
            // INIT
            Thread.sleep(60);
            System.out.println("new iteration");
            // set current token
            token = player ? 'X' : 'O';

            // INPUT && UPDATE
            if (player || twoPlayer) {
                // process exclusively on mouse input
                if (StdDraw.isMousePressed()) {
                    // process input and query move status
                    // cycle role ONLY if legal move was performed
                    // (returns true on legal move, whereas next cycle
                    // requires player to be false)
                    player = !processInput(gameBoard, size, token);
                }
            } else {
                System.out.println(" - ANALYSIS - ");
                analysis = minimax(gameBoard, player, maxDepth);
                System.out.println(" --- END ---- ");
                // act according to anlysis
                gameBoard[analysis[0]][analysis[1]] = token;
                // cycle role
                player = !player;
            }

            // RENDER
            drawGameBoard(gameBoard, size);
            System.out.println(Arrays.toString(gameBoard[2]));
            System.out.println(Arrays.toString(gameBoard[1]));
            System.out.println(Arrays.toString(gameBoard[0]));
            System.out.println(StdDraw.mouseX());
            System.out.println(StdDraw.mouseY());
            System.out.println(player);
            System.out.println(checkIfFull(gameBoard));
            System.out.println(checkIfWinner(gameBoard, player));
        // exit game loop upon ESC input, full board or on fulfilled winning condition
        } while (!StdDraw.isKeyPressed(0x1b) && !checkIfFull(gameBoard) && !checkIfWinner(gameBoard, player));

        // draw who won, if anybody won
        if (checkIfWinner(gameBoard, !player)) {
            // draw little box behind winner text
            StdDraw.setPenColor(Color.BLACK);
            StdDraw.filledRectangle(size / 2d, size / 2d, size / 4d, size / 32d);
            // draw winner text
            StdDraw.setPenColor(Color.WHITE);
            String winnerText = "winner is " + (!player ? "human1" : (twoPlayer ? "human2" : "comp"));
            StdDraw.text(size / 2d, size / 2d, winnerText);
        }

        System.out.println("finished");
    }

    // returns whether legal move was performed
    private static boolean processInput(char[][] gameBoard, int size, char token) {
        // init
        int step = size / gameBoard.length;
        int mouseX = (int) Math.floor(StdDraw.mouseX() / step);
        int mouseY = (int) Math.floor(StdDraw.mouseY() / step);

        // check if move is legal
        if (gameBoard[mouseY][mouseX] != ' ') {
            System.out.println("column/row " + mouseY + "/" + mouseX + " already occupied!");
            return false;
        }

        gameBoard[mouseY][mouseX] = token;
        return true;
    }

    private static int[] minimax(char[][] gameBoard, boolean player, int depth) {
        int[] retArray = new int[gameBoard.length];
        // if player is true set retArray[2] to int max, else int min
        retArray[2] = player ? Integer.MAX_VALUE : Integer.MIN_VALUE;

        for (int row = 0; row < gameBoard.length; row++) {
            for (int col = 0; col < gameBoard.length; col++) {
                // ignore field if not empty
                if (gameBoard[row][col] != ' ') continue;

                // set current field to player specific token temporarily
                gameBoard[row][col] = player ? 'X' : 'O';

                // save analysis res depending on analysis
                if (checkIfWinner(gameBoard, true)) {
                    retArray[0] = row;
                    retArray[1] = col;
                    retArray[2] = -1;
                } else if (checkIfWinner(gameBoard, false)) {
                    retArray[0] = row;
                    retArray[1] = col;
                    retArray[2] = 1;
                } else if (checkIfFull(gameBoard) || depth - 1 == 0) {
                    retArray[0] = row;
                    retArray[1] = col;
                    retArray[2] = 0;
                } else {
                    // try deeper analysis
                    int[] tempArray = minimax(gameBoard, !player, depth - 1);
                    // if deeper analysis shows more promising result,
                    // override own analysis results
                    if (player) {
                        if (tempArray[2] < retArray[2]) {
                            retArray[0] = tempArray[0];
                            retArray[1] = tempArray[1];
                            retArray[2] = tempArray[2];
                        }
                    } else {
                        if (tempArray[2] > retArray[2]) {
                            retArray[0] = tempArray[0];
                            retArray[1] = tempArray[1];
                            retArray[2] = tempArray[2];
                        }
                    }
                }

                // reset temporarily set field
                gameBoard[row][col] = ' ';
            }
        }

        System.out.println(retArray[2]);

        return retArray;
    }
    
    private static boolean checkIfFull(char[][] gameBoard) {
        for (char[] row : gameBoard)
            for (char character : row)
                if (character == ' ') return false;
        return true;
    }
    
    private static boolean checkIfWinner(char[][] gameBoard, boolean player) {
        int len = gameBoard.length;

        // as every row and line as well as 2 diagonals have to be evaluated,
        // logic can be expressed in a single for loop, where each iteration
        // handles another row + column
        // the non-edge indices also query diagonals
        // field size is implicitly 3x3

        // row, col, diagonal 1 and diagonal 2
        boolean row, col, di1, di2;

        // for loop checks:
        // X - -
        // - X -
        // - - X
        for (int i = 0; i < len; i++) {
            // skip empty fields
            if (gameBoard[i][i] == ' ') continue;

            // init
            row = true;
            col = true;
            di1 = true;
            di2 = true;

            // perform col check
            if (gameBoard[i][i] != gameBoard[i][(i + 1) % len]) col = false;
            if (gameBoard[i][i] != gameBoard[i][(i + 2) % len]) col = false;

            // perform row check
            if (gameBoard[i][i] != gameBoard[(i + 1) % len][i]) row = false;
            if (gameBoard[i][i] != gameBoard[(i + 2) % len][i]) row = false;

            // perform diagonal check
            // skip diagonal check for edges
            if (i == 0 || i == len - 1) {
                di1 = false;
                di2 = false;
            }
            else {
                if (gameBoard[i][i] != gameBoard[i - 1][i - 1]) di1 = false;
                if (gameBoard[i][i] != gameBoard[i + 1][i + 1]) di1 = false;
                if (gameBoard[i][i] != gameBoard[i + 1][i - 1]) di2 = false;
                if (gameBoard[i][i] != gameBoard[i - 1][i + 1]) di2 = false;
            }

            if (row || col || di1 || di2) return true;
        }
        return false;
    }

    private static void drawGameBoard(char[][] gameBoard, int size) {
        // init
        int step = size / gameBoard.length;

        // clear previous board
        StdDraw.clear();

        // draw separation lines
        for (int i = step; i < size; i += step) {
            StdDraw.line(0d, i, size, i);
            StdDraw.line(i, 0d, i, size);
        }

        // iterate symbols to draw
        for (int row = 0; row < gameBoard.length; row++) {
            for (int col = 0; col < gameBoard[row].length; col++) {
                // access matrix with order identical to drawing
                // this is already satisfied due to processInput implementation
                switch (gameBoard[row][col]) {
                    case 'X':
                        // line from lower left corner to upper right corner
                        StdDraw.line(col * step, row * step, col * step + step, row * step + step);
                        // line from lower right corner to upper left corner
                        StdDraw.line(col * step + step, row * step, col * step, row * step + step);
                        break;
                    case 'O':
                        // circle from center with radius step / 2d
                        StdDraw.circle(col * step + step / 2d, row * step + step / 2d, step / 2d);
                        break;
                }
            }
        }
    }
}
